﻿#region Using's

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using LightPersist.Exceptions;
using LightPersist.Metainfo;

#endregion

namespace LightPersist
{
	public interface ILightList : IList<ModelBase>
	{
		string PropertyName { get; }
		ModelBase Owner { get; }
	}

	internal class LightListCore : ILightList
	{
		readonly LightPersistContext _context;
		protected internal LightPersistContext Context { get { return _context; } }
		private readonly bool _isReadOnly;
		private readonly Type _elementType;

		#region Constructor

		public LightListCore(LightPersistContext context, ModelBase owner, PropertyMetainfo propertyMetainfo)//,  TypeName elementType, string propName)
		{
			//if (elementType == null)
			//{
			//    throw new ArgumentNullException("elementType");
			//}

			if (propertyMetainfo == null)
			{
				throw new ArgumentNullException("propertyMetainfo");
			}

			_context = context;
			_elementType = propertyMetainfo.TypeClr; //elementType;
			_isReadOnly = propertyMetainfo.IsReadonly; //owner.ObjectState.ClassMetainfo.GetPropertyInfo(propName).IsReadonly;
			PropertyName = propertyMetainfo.Name; //propName;
			Owner = owner;
		}

		#endregion

		#region Properties

		// represent the owner and property name of the list
		// for example - Control.Children
		public ModelBase Owner { get; private set; }

		public string PropertyName { get; private set; }

		internal ILightListProxyInternal LightListProxyInternal { get; set; }

		public List<Reference> ReferenceList
		{
			get
			{
				var propList = Owner.ObjectState.Get(PropertyName);
				return (List<Reference>)propList;
			}
		}

		public Type ElementType { get { return _elementType; } }

		#endregion

		/// <summary>
		/// Return the type of the reference (Strong/Weak)
		/// </summary>
		/// <returns>True is property stores Strong reference. False in other cases</returns>
		internal bool IsStrongReferenceProperty()
		{
			return Owner.ObjectState.ClassMetainfo.GetPropertyInfo(PropertyName).RefType
							== ReferenceType.Strong;
		}


		#region IList<T> Members

		public void RemoveAt(int index)
		{
			EnsureListModifiable();
			_context.ListRemoveAt(this, index, ElementType);
		}

		public ModelBase this[int index]
		{
			get
			{
				var itemType = ReferenceList[index].TypeOfProperty;
				return _context.GetListValue(this, index, itemType);
			}
			set
			{
				RemoveAt(index);
				Insert(index, value);
			}
		}

		public void Add(ModelBase item)
		{
			Insert(Count, item);
		}

		public void Clear()
		{
			// Typically the cheapest way is to remove from the end.
			for (var i = Count - 1; i >= 0; i--)
			{
				RemoveAt(i);
			}
		}

		public bool Contains(ModelBase item)
		{
			// TODO: Provide more efficient solution
			return ReferenceList.Exists(x => x.ID == item.ID);
		}

		public void CopyTo(ModelBase[] array, int arrayIndex)
		{
			// TODO: Does lazy init here is efficiently enough?
			if (arrayIndex != 0)
			{
				throw new NotSupportedException("[arrayIndex != 0] Write couple of tests for such behavior and be happy");
			}

			var result = ReferenceList.Select(x => _context.GetObjectByID(x.ID, x.TypeOfProperty)).ToArray();

			if (result.Length != array.Length)
			{
				throw new ArgumentException("The size of array to copy to is invalid!");
			}

			for (int i = 0, m = result.Length; i < m; i++)
			{
				array[i] = result[i];
			}
		}

		public int Count
		{
			get { return ReferenceList.Count; }
		}

		public bool IsReadOnly
		{
			get
			{
				return _isReadOnly;
			}
		}

		private void EnsureListModifiable()
		{
			if (IsReadOnly)
			{
				throw new LightPersistException("List is readonly");
			}
		}

		public bool Remove(ModelBase item)
		{
			// TODO: Provide more efficient solution
			var index = IndexOf(item);
			if (index == -1)
			{
				return false;
			}
			RemoveAt(index);
			//Notify(item, index, false);
			return true;
		}

		public IEnumerator<ModelBase> GetEnumerator()
		{
			// TODO: Provide more efficient solution
			return this.ToArray().Cast<ModelBase>().GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion

		public int IndexOf(ModelBase item)
		{
			return ReferenceList.FindIndex(x => x.ID == item.ID);
		}

		public void Insert(int index, ModelBase item)
		{
			EnsureListModifiable();
			_context.ListInsert(this, index, item);
		}

		internal void Notify(ModelBase item, int index, bool isInsert)
		{
			if (LightListProxyInternal != null)
			{
				var args = isInsert
							? NotifyCollectionArgsFactory.CreateInsert(item, index)
							: NotifyCollectionArgsFactory.CreateRemove(item, index);
				LightListProxyInternal.OnCollectionChanged(args);
			}
		}
	}
}